﻿using MudEngine.Parsing;
using System;
using System.Collections.Generic;
using System.Text;

namespace MudEngine.Runtime
{
    public class object_t
    {
		public const string INVOKE_CREATE = "create";
        public const string INVOKE_INIT = "init";

        private static long _object_id = 1;
		/// <summary>
		/// 
		/// </summary>
		private Program _program;
		/// <summary>
		/// 
		/// </summary>
		private Value[] _variables;
		/// <summary>
		/// 
		/// </summary>
		private List<object_t> _contains;
		/// <summary>
		/// Holds the last used value
		/// </summary>
		private DateTime _lastUsed;
		/// <summary>
		/// Pointer to the interpreter for executing the program code
		/// </summary>
		private Interpreter _interpreter;
        /// <summary>
        /// 
        /// </summary>
        public long Id { get; private set; }
		/// <summary>
		/// Name of this object
		/// </summary>
		public string Name { get; set; }
		/// <summary>
		/// 
		/// </summary>
		public ObjectFlags Flags { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public bool PreventDestroy { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public bool IsInteractive
        {
            get
            {
                return this.TcpConnection != null;
            }
        }
		/// <summary>
		/// Pointer to the TCP connection object if this object is an interactive user
		/// </summary>
		public TcpConnection TcpConnection { get; set; }
		/// <summary>
		/// Holds the program structure of this object
		/// </summary>
		public Program Program
		{
			get
			{
				return _program;
			}
			set
			{
				this._program = value;
				this._variables = new Value[this._program.TotalNumberOfVariables];

                for (int i = 0; i < _variables.Length; i++)
                    this._variables[i] = Value.Null;
			}
		}
		/// <summary>
		/// Holds the global variable values of this object
		/// </summary>
		public Value[] Variables
		{
			get
			{
				return this._variables;
			}
		}
		/// <summary>
		/// What objects does this object contain
		/// </summary>
		public List<object_t> Contains
		{
			get
			{
				if (this._contains == null)
					this._contains = new List<object_t>();
				return this._contains;
			}
		}
		/// <summary>
		/// Last time object was referenced. Should be used by the garbage collector. Not implemented yet.
		/// </summary>
		public DateTime LastUsed
		{
			get { return _lastUsed; }
		}
		/// <summary>
		/// Object surrounding us
		/// </summary>
		public object_t Super { get; set; }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="interpreter"></param>
		public object_t(string name, Interpreter interpreter)
		{
            this.Id = object_t._object_id++;

			this._interpreter = interpreter;
			this.Name = name;

			this.Touch();
		}
        /// <summary>
		/// 
		/// </summary>
		public void Touch()
		{
			this._lastUsed = DateTime.Now;
		}
		/// <summary>
		/// 
		/// </summary>
		public void Create()
		{
			_interpreter.Call___INIT(this);
            _interpreter.Apply(this, INVOKE_CREATE, 0);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return this.Name;
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            object_t other = obj as object_t;
            if (other == null)
                return false;

            return this.Id == other.Id;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return (int)this.Id;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="o2"></param>
        public bool Move(object_t o2)
        {
            if (this.Super != null)
            {
                if (this.Super.Contains.Contains(this))
                {
                    if (!this.Super.Contains.Remove(this))
                        return false;
                    this.Super = null;
                }
            }
            o2.Contains.Add(this);
            this.Super = o2;

            this.ReinitializeEnvironment(o2);

            return true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="destination"></param>
        public void ReinitializeEnvironment(object_t destination)
        {
            // might need to add some checking if the object has an init function to minimize the load on calling not-existing functions
            // if (destination.HasFunction("init")) OR if (destination.Flags.HasFlag(ObjectFlags.O_INIT))
            _interpreter.Context.SaveCaller(this);
            _interpreter.Apply(destination, INVOKE_INIT, 0);
            _interpreter.Context.RestoreCaller();
            // do we need more checking on the current object after calling init on it? Is there a change the environment of the object has been changed in the process?
            // if (destination != this.Super)
            //  return;

            foreach (object_t ob in destination.Contains)
            {
                if (ob == this)
                    continue;
                if (ob.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                    _interpreter.Error("INIT called on destructed object {0}", ob);

                _interpreter.Context.SaveCaller(ob);
                _interpreter.Apply(this, INVOKE_INIT, 0);
                _interpreter.Context.RestoreCaller();

                if (destination != this.Super)
                    return;

                if (this.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                    _interpreter.Error("INIT called on destructed object {0}", this);
                if (ob.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                    _interpreter.Error("INIT called on destructed object {0}", ob);

                _interpreter.Context.SaveCaller(this);
                _interpreter.Apply(ob, INVOKE_INIT, 0);
                _interpreter.Context.RestoreCaller();

                if (destination != this.Super)
                    return;
            }
            if (destination.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                _interpreter.Error("INIT called on destructed object {0}", destination);
            if (this.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                _interpreter.Error("INIT called on destructed object {0}", this);
            _interpreter.Context.SaveCaller(destination);
            _interpreter.Apply(this, INVOKE_INIT, 0);
            _interpreter.Context.RestoreCaller();
        }

        /// <summary>
        /// 
        /// </summary>
        public void Destruct()
        {
            if (this.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                return;

            this.Flags |= ObjectFlags.O_DESTRUCTED;
            this.PreventDestroy = false;

            if (this.Super != null)
            {
                while (this.Contains.Count > 0)
                    this.Contains[0].Move(this.Super);

                if (this.Super.Contains.Contains(this))
                {
                    if (!this.Super.Contains.Remove(this))
                        Log.Fatal(this.GetType(), string.Format("Unable to remove {0} from its super {1}", this, this.Super));
                    this.Super = null;
                }
            }

            if (this.IsInteractive)
            {
                this.TcpConnection.Disconnect();
            }
        }
    }

	[Flags]
	public enum ObjectFlags : int
	{
		O_MASTER = 1,
		O_USER = 2,
		O_ECHO = 4,
		O_CLONE = 8,
		O_INTERACTIVE = 16,
        O_HEARTBEAT = 32,

		O_DESTRUCTED = 0xFF
	}
}
